﻿// --------------------------------------------------------------------------------------------------------------------
// <summary>
//   CheckEventLogStep.cs
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace TRlib.BizUnit.CustomSteps
{
    using System;
    using System.Diagnostics;
    using System.Text.RegularExpressions;
    using System.Threading;
    using global::BizUnit;

    /// <summary>
    /// The CheckEventLogStep checks event log entries 
    /// </summary>
    /// 
    /// <remarks>
    /// The following shows an example of the Xml representation of this test step.
    /// 
    /// <code escaped="true">
    ///    <TestStep assemblyPath="" typeName="CheckEventLogStep">
    ///        <Retry>
    ///           <RetryAttempts>30</RetryAttempts>
    ///           <RetryIntervalMs>1000</RetryIntervalMs>
    ///        </Retry>
    ///        <CheckNEvents>10</CheckNEvents>
    ///        <CheckEventsInLastNMinutes>2</CheckEventsInLastNMinutes>
    ///        <EventLogSource>MyEventLogSource</EventLogSource>
    ///        <MatchPattern>SomeRegexExpression</MatchPattern>
    ///    </TestStep>
    /// </code>
    ///    
    ///    <list type="table">
    ///        <listheader>
    ///            <term>Tag</term>
    ///            <description>Description</description>
    ///        </listheader>
    ///        <item>
    ///            <term>Retry/RetryAttempts</term>
    ///            <description>Number of retry attempts allowed</description>
    ///        </item>
    ///        <item>
    ///            <term>Retry/RetryIntervalMs</term>
    ///            <description>Interval of retries in miliseconds.</description>
    ///        </item>
    ///        <item>
    ///            <term>CheckNEvents</term>
    ///            <description>Number of events, starting at most recent, to check. Set to 0 if using CheckEventsInLastNMinutes only</description>
    ///        </item>
    ///        <item>
    ///            <term>CheckEventsInLastNMinutes</term>
    ///            <description>Number of minutes back in the log to check. Set to 0 if using CheckNEvents only</description>
    ///        </item>
    ///        <item>
    ///            <term>EventLogSource</term>
    ///            <description>Event log source</description>
    ///        </item>
    ///        <item>
    ///            <term>MatchPattern</term>
    ///            <description>The regec expression to check the event body against</description>
    ///        </item>
    ///    </list>
    /// </remarks>
    public class CheckEventLogStep : ITestStep
    {
        #region ITestStep Members
        public void Execute(System.Xml.XmlNode testConfig, Context context)
        {
            DateTime stepStart = DateTime.Now;
            DateTime searchFrom;
            int eventCount = 0;

            // Read config
            string source = context.ReadConfigAsString(testConfig, "EventLogSource");
            string matchPattern = context.ReadConfigAsString(testConfig, "MatchPattern");

            // Retry
            int retryAttempts = 0;
            int retryDelay = 0;
            if (testConfig.SelectSingleNode("Retry") != null)
            {
                retryAttempts = context.ReadConfigAsInt32(testConfig, "Retry/RetryAttempts");
                retryDelay = context.ReadConfigAsInt32(testConfig, "Retry/RetryIntervalMs");
            }

            // Search cut-off 
            int eventsToCheck = context.ReadConfigAsInt32(testConfig, "CheckNEvents");
            int minutesToCheck = context.ReadConfigAsInt32(testConfig, "CheckEventsInLastNMinutes");
            if (eventsToCheck == 0 && minutesToCheck == 0)
            {
                throw new CustomBizUnitExecutionException("Cannot resolve search cut-off based on number of events or amount of time.");
            }

            // Resolve search cut-off instruction by number of events
            if (eventsToCheck == 0)
            {
                // Some arbitrary large number
                eventsToCheck = 200;
            }

            // Resolve search cut-off instruction by number of minutes
            if (minutesToCheck == 0)
            {
                // Some arbitrary distant past time
                searchFrom = DateTime.Now.AddMinutes(-100);
            }
            else
            {
                searchFrom = stepStart.AddMinutes(0 - minutesToCheck);
            }

            // Get the log
            EventLog eventLog = new EventLog();
            eventLog.Source = source;

            // Retry loop
            bool found = false;

            // Set retry flag on if retry attempts are > 0. 
            bool retry = retryAttempts > 0;
            int tryCount = 0;

            // tryCount always zero on first run
            while (retry || tryCount == 0)
            {
                // Increment try-count
                tryCount++;

                // Retry scope - inside here any exceptions will be silent as long as there are retries pending.
                try
                {
                    // Fix the event log entry collection at this point in time
                    EventLogEntryCollection entries = eventLog.Entries;

                    // Fix the count of events at this point in time
                    int eventLogEntriesCount = eventLog.Entries.Count;

                    // Work backwards through log and check each event.
                    eventCount = 0;
                    for (int i = eventLogEntriesCount - 1; i >= 0; i--)
                    {
                        // Get this entry
                        EventLogEntry entry = entries[i];
                        eventCount++;

                        // Can continue based on time cut-off?
                        if (DateTime.Compare(entry.TimeGenerated, searchFrom) < 0)
                        {
                            context.LogInfo(String.Format("Time cut-off {0} reached for event at {1}.", searchFrom, entry.TimeGenerated));
                            break;
                        }

                        // Can continue based on number of events cut-off?
                        if (eventCount > eventsToCheck)
                        {
                            context.LogInfo(String.Format("Event count cut-off {0} reached for event {1}.", eventsToCheck, eventCount));
                            break;
                        }

                        // Perform match
                        Match match = Regex.Match(entry.Message, matchPattern);
                        if (match.Success)
                        {
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                    {
                        throw new CustomBizUnitExecutionException(String.Format("Could not find match for {0} against any of the {1} events searched.", matchPattern, eventCount));
                    }

                    // If no exception thrown by this point then has been successful. So set retry = false
                    retry = false;
                    context.LogInfo(String.Format("Found event matching expression {0} at position {1} in log.", matchPattern, eventCount));
                }
                catch (Exception)
                {
                    // Work out whether to suppress exception for another retry...
                    if (tryCount >= retryAttempts)
                    {
                        // If try-count >= retry attempts - throw to caller
                        throw;
                    }

                    // Otherwise supress exception and delay before continuing
                    Thread.Sleep(retryDelay);
                    context.LogInfo(String.Format("Retry attempt {0} of {1}", tryCount, retryAttempts));
                } // catch
            } // while retry or trycount = 0
        }
        #endregion
    }
}
